package com.jzo2o.health.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.constants.UserType;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.model.msg.TradeStatusMsg;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.health.constant.RedisConstants;
import com.jzo2o.health.constant.TradeConstants;
import com.jzo2o.health.enums.OrderPayStatusEnum;
import com.jzo2o.health.enums.OrderStatusEnum;
import com.jzo2o.health.mapper.OrdersMapper;
import com.jzo2o.health.mapper.ReservationSettingMapper;
import com.jzo2o.health.model.UserThreadLocal;
import com.jzo2o.health.model.domain.*;
import com.jzo2o.health.model.domain.Member;
import com.jzo2o.health.model.domain.Orders;
import com.jzo2o.health.model.domain.OrdersCancelled;
import com.jzo2o.health.model.domain.Setmeal;
import com.jzo2o.health.model.dto.OrderCountDTO;
import com.jzo2o.health.model.dto.request.OrdersPageQueryReqDTO;
import com.jzo2o.health.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.health.model.dto.response.*;
import com.jzo2o.health.properties.TradeProperties;
import com.jzo2o.health.service.*;
import com.jzo2o.health.service.client.NativePayClient;
import com.jzo2o.health.service.client.TradingClient;
import com.jzo2o.mysql.utils.PageUtils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author xd
 */
@Service
@Slf4j
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersService {
    @Resource
    private ISetmealService setmealService;
    @Resource
    private IMemberService memberService;
    @Resource
    private NativePayClient nativePayClient;
    @Resource
    private ReservationSettingMapper reservationSettingMapper;
    @Resource
    private TradeProperties tradeProperties;
    @Resource
    private TradingClient tradingClient;
    @Resource
    private IOrdersCancelledService ordersCancelledService;
    @Resource
    private IOrdersManagerService ordersManagerService;

    @Resource
    private IOrdersService currentProxy;
    @Resource
    private RedissonClient redissonClient;

    /**
     * @author xd
     * 下单接口
     * @param placeOrderReqDTO {@code PlaceOrderReqDTO}下单请求信息
     * @return {@code PlaceOrderResDTO}下单响应信息
     */
    @Override
    public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO) {
        //判断当前的预约人数是否达到上限
        ReservationSetting reservationSetting = reservationSettingMapper.selectByDate(placeOrderReqDTO.getReservationDate());
        if (Objects.equals(reservationSetting.getNumber(), reservationSetting.getReservations())) {
            throw new ForbiddenOperationException("今天的预约人数已满,请另择时间");
        }
        //1.根据套餐id查询套餐信息
        Setmeal setmeal = setmealService.getById(placeOrderReqDTO.getSetmealId());
        if (ObjectUtil.isNull(setmeal)) {
            throw new ForbiddenOperationException("选择的套餐不存在");
        }
        //2.封装订单实体类，插入数据库中
        Orders orders = new Orders();
        orders.setOrderStatus(OrderStatusEnum.NO_PAY.getStatus());
        orders.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());
        orders.setSetmealId(setmeal.getId());
        orders.setSetmealName(setmeal.getName());
        orders.setSetmealSex(setmeal.getSex());
        orders.setSetmealAge(setmeal.getAge());
        orders.setSetmealImg(setmeal.getImg());
        orders.setSetmealRemark(setmeal.getRemark());
        orders.setSetmealPrice(BigDecimal.valueOf(setmeal.getPrice()));
        orders.setReservationDate(placeOrderReqDTO.getReservationDate());
        orders.setCheckupPersonSex(placeOrderReqDTO.getCheckupPersonSex());
        orders.setCheckupPersonName(placeOrderReqDTO.getCheckupPersonName());
        orders.setCheckupPersonPhone(placeOrderReqDTO.getCheckupPersonPhone());
        orders.setCheckupPersonIdcard(placeOrderReqDTO.getCheckupPersonIdcard());
        //根据用户id查询用户信息
        Member member = memberService.getById(UserThreadLocal.currentUser().getId());
        if (ObjectUtil.isNull(member)) {
            throw new ForbiddenOperationException("获取用户信息失败");
        }
        orders.setMemberId(member.getId());
        orders.setMemberPhone(member.getPhone());
        orders.setSortBy(DateUtil.current());
        //3.异步插入数据并修改预约人数
        try {
            currentProxy.saveOrdersAndUpdateReservation(orders, reservationSetting.getId());
        } catch (InterruptedException e) {
            log.error("保存订单并修改预约人数出现异常", e);
        }
        //成功则返回订单id
        PlaceOrderResDTO placeOrderResDTO = new PlaceOrderResDTO();
        placeOrderResDTO.setId(orders.getId());
        return placeOrderResDTO;
    }

    /**
     * 保存订单并修改预约人数
     * @author xd
     * @param orders {@code Orders}订单实体类
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveOrdersAndUpdateReservation(Orders orders, Integer reservationId) throws InterruptedException {
        //保存订单
        boolean rs = save(orders);
        if (!rs) {
            throw new DBException("订单添加失败");
        }
        try {
            //加锁
            boolean lock = redissonClient.getLock(RedisConstants.UPDATE_RESERVATION_LOCK).tryLock(5, -1, TimeUnit.SECONDS);
            if (!lock) {
                throw new RuntimeException("操作过于频繁,请稍后再试");
            }
            //修改预约人数
            Integer i = reservationSettingMapper.updateReservations(reservationId);
            if (i == 0) {
                //说明修改失败
                throw new DBException("修改已预约人数失败");
            }
        } finally {
            //释放锁
            redissonClient.getLock(RedisConstants.UPDATE_RESERVATION_LOCK).unlock();
        }

    }

    /**
     * @author xd
     * 订单支付申请
     * @param id {@code Long}订单id
     * @param tradingChannel {@code PayChannelEnum} 支付渠道
     * @return {@code OrdersPayResDTO} 订单支付响应体
     */
    @Override
    public OrdersPayResDTO pay(Long id, PayChannelEnum tradingChannel) {
        OrdersPayResDTO res = new OrdersPayResDTO();
        //1.根据订单id查询订单
        Orders orders = getById(id);
        if (ObjectUtil.isNull(orders)) {
            throw new ForbiddenOperationException("订单不存在");
        }
        //2.判断订单的支付状态是否未支付,如果不是则直接return结果
        if (ObjectUtil.notEqual(orders.getPayStatus(), OrderPayStatusEnum.NO_PAY.getStatus())) {
            res.setPayStatus(orders.getPayStatus());
            res.setProductOrderNo(orders.getId());
            res.setTradingOrderNo(orders.getTradingOrderNo());
            res.setTradingChannel(orders.getTradingChannel());
            return res;
        }
        //3.封装数据,调用支付服务
        NativePayReqDTO dto = new NativePayReqDTO();
        dto.setMemo("加麻加辣");
        dto.setProductOrderNo(id);
        dto.setChangeChannel(ObjectUtil.equal(orders.getTradingChannel(), tradingChannel.getValue()));
        dto.setTradingChannel(tradingChannel);
        dto.setProductAppId(TradeConstants.PRODUCT_APP_ID);
        dto.setTradingAmount(orders.getSetmealPrice());
        if (ObjectUtil.equal(tradingChannel, PayChannelEnum.WECHAT_PAY)) {
            dto.setEnterpriseId(tradeProperties.getWechatEnterpriseId());
        } else if (ObjectUtil.equal(tradingChannel, PayChannelEnum.ALI_PAY)) {
            dto.setEnterpriseId(tradeProperties.getAliEnterpriseId());
        } else {
            throw new ForbiddenOperationException("暂不支持该支付渠道");
        }
        //调用处理好降级的支付接口
        NativePayResDTO nativePayResDTO = nativePayClient.payOrder(dto);
        if (ObjectUtil.isNull(nativePayResDTO)) {
            throw new ForbiddenOperationException("支付失败");
        }
        //4.不为空，修改数据库字段值
        boolean update = lambdaUpdate()
                .set(Orders::getTradingChannel, nativePayResDTO.getTradingChannel())
                .set(Orders::getTradingOrderNo, nativePayResDTO.getTradingOrderNo())
                .eq(Orders::getId, id).update();
        //判断是否修改成功
        if (!update) {
            throw new DBException("修改订单字段失败");
        }
        //5.封装返回结果
        BeanUtil.copyProperties(nativePayResDTO, res);
        return res;
    }

    /**
     * 更新支付结果
     *
     * @author XingDong
     */
    @Override
    public void paySuccess(TradeStatusMsg tradeStatusMsg) {
        boolean update = lambdaUpdate()
                .set(Orders::getPayTime, LocalDateTime.now())
                .set(Orders::getPayStatus, OrderPayStatusEnum.PAY_SUCCESS.getStatus())
                .set(Orders::getOrderStatus, OrderStatusEnum.WAITING_CHECKUP.getStatus())
                .set(Orders::getTradingChannel, tradeStatusMsg.getTradingChannel())
                .set(Orders::getTransactionId, tradeStatusMsg.getTransactionId())
                .eq(Orders::getId, tradeStatusMsg.getProductOrderNo())
                .update();

        if (!update) {
            throw new ForbiddenOperationException("更新订单id：" + tradeStatusMsg.getProductOrderNo() + " 支付信息失败");
        }
    }

    /**
     * @param id {@code Long} 订单id
     * @return {@code OrdersPayResDTO} 订单支付响应体
     * @author xd
     * 手动获取支付结果
     */
    @Override
    public OrdersPayResDTO getPayResult(Long id) {
        //1.判断订单是否存在
        Orders orders = getById(id);
        if (ObjectUtil.isNull(orders)) {
            throw new ForbiddenOperationException("订单不存在");
        }
        OrdersPayResDTO res = new OrdersPayResDTO();
        //2.判断是否在查询时刚好定时任务执行完成,已经修改了状态
        if (ObjectUtil.equal(orders.getPayStatus(), OrderPayStatusEnum.NO_PAY.getStatus()) && orders.getTransactionId() == null) {
            //3.说明没被修改状态,需要查询结果
            TradingResDTO result = tradingClient.getTradResult(orders.getTradingOrderNo());
            //4.判断结果是否不为空且状态是否为已结算,如果是则修改数据库数据
            if (ObjectUtil.isNull(result)) {
                throw new ForbiddenOperationException("无该订单支付记录");
            }
            if (result.getTradingState().equals(TradingStateEnum.YJS)) {
                //需要更新数据库数据
                TradeStatusMsg tradeStatusMsg = new TradeStatusMsg();
                tradeStatusMsg.setTransactionId(result.getTransactionId());
                tradeStatusMsg.setTradingChannel(result.getTradingChannel());
                tradeStatusMsg.setProductOrderNo(result.getProductOrderNo());
                paySuccess(tradeStatusMsg);
                //封装数据返回
                BeanUtil.copyProperties(result, res);
                res.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
                return res;
            }
        }
        //5.被修改了状态,直接封装数据进行返回
        res.setTradingChannel(orders.getTradingChannel());
        res.setPayStatus(orders.getPayStatus());
        res.setProductOrderNo(orders.getId());
        res.setTradingOrderNo(orders.getTradingOrderNo());
        return res;
    }

    /**
     * 分页查询
     *
     * @param ordersPageQueryReqDTO
     * @return
     */
    @Override
    public PageResult<OrdersResDTO> adminPageQuery(OrdersPageQueryReqDTO ordersPageQueryReqDTO) {

        ordersPageQueryReqDTO.setOrderBy1("create_time");

        Page<Orders> page = PageUtils.parsePageQuery(ordersPageQueryReqDTO, Orders.class);

        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery()
                .eq(ObjectUtil.isNotEmpty(ordersPageQueryReqDTO.getOrderStatus()), Orders::getOrderStatus, ordersPageQueryReqDTO.getOrderStatus())
                .likeRight(StrUtil.isNotBlank(ordersPageQueryReqDTO.getMemberPhone()), Orders::getMemberPhone, ordersPageQueryReqDTO.getMemberPhone());

        Page<Orders> ordersPage = baseMapper.selectPage(page, queryWrapper);

        return PageUtils.toPage(ordersPage, OrdersResDTO.class);
    }

    /**
     * 根据订单id查询
     *
     * @param id
     * @return
     */
    @Override
    public AdminOrdersDetailResDTO aggregation(Long id) {

        //根据订单id查询订单信息
        Orders order = getById(id);

        if (order == null) {
            throw new BadRequestException("订单不存在");
        }

        //封装订单信息
        AdminOrdersDetailResDTO dto = new AdminOrdersDetailResDTO();
        AdminOrdersDetailResDTO.OrderInfo orderInfo = BeanUtil.copyProperties(order, AdminOrdersDetailResDTO.OrderInfo.class);
        dto.setOrderInfo(orderInfo);

        //封装订单支付信息
        AdminOrdersDetailResDTO.PayInfo payInfo = new AdminOrdersDetailResDTO.PayInfo();
        if (ObjectUtil.equal(order.getPayStatus(), OrderPayStatusEnum.NO_PAY.getStatus())) {
            //如果订单的支付状态为 未支付 ，设置支付信息中的支付状态为 未支付
            payInfo.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());
        } else if (ObjectUtil.notEqual(order.getPayStatus(), OrderPayStatusEnum.NO_PAY.getStatus())) {
            //如果订单的支付状态不为 未支付 ，设置支付信息中的支付状态为 已支付
            payInfo.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
        }
        payInfo.setTradingChannel(order.getTradingChannel());
        payInfo.setThirdOrderId(order.getTransactionId());
        payInfo.setPayTime(order.getPayTime());
        dto.setPayInfo(payInfo);

        //查询订单取消信息
        OrdersCancelled ordersCancelled = ordersCancelledService.lambdaQuery()
                .eq(OrdersCancelled::getId, order.getId())
                .one();

        if (ObjectUtil.isNotEmpty(ordersCancelled)) {
            //封装订单取消信息
            AdminOrdersDetailResDTO.CancelInfo cancelInfo = BeanUtil.copyProperties(ordersCancelled, AdminOrdersDetailResDTO.CancelInfo.class);
            dto.setCancelInfo(cancelInfo);

            //封装订单退款信息
            if (ObjectUtil.equal(order.getPayStatus(), OrderPayStatusEnum.REFUNDING.getStatus()) ||
                    ObjectUtil.equal(order.getPayStatus(), OrderPayStatusEnum.REFUND_FAIL.getStatus()) ||
                    ObjectUtil.equal(order.getPayStatus(), OrderPayStatusEnum.REFUND_FAIL.getStatus())
            ) {
                AdminOrdersDetailResDTO.RefundInfo refundInfo = new AdminOrdersDetailResDTO.RefundInfo();
                refundInfo.setRefundStatus(order.getPayStatus());
                refundInfo.setCancelTime(cancelInfo.getCancelTime());
                refundInfo.setCancelReason(cancelInfo.getCancelReason());
                refundInfo.setTradingChannel(order.getTradingChannel());
                refundInfo.setRefundId(order.getRefundId());
                dto.setRefundInfo(refundInfo);
            }
        }
        return dto;
    }

    /**
     * 滚动分页查询
     *
     * @param ordersStatus
     * @param sortBy
     * @return
     */
    @Override
    public List<OrdersResDTO> userPageQuery(Integer ordersStatus, Long sortBy) {
        List<Orders> list = lambdaQuery()
                .eq(ObjectUtil.isNotEmpty(ordersStatus), Orders::getOrderStatus, ordersStatus)
                .gt(ObjectUtil.isNotEmpty(sortBy), Orders::getSortBy, sortBy)
                .orderByDesc(Orders::getSortBy)
                .list();
        return BeanUtil.copyToList(list, OrdersResDTO.class);
    }

    /**
     * 根据订单id查询
     *
     * @param id
     * @return
     */
    @Override
    public OrdersDetailResDTO detail(Long id) {

        //查询订单信息
        Orders order = getById(id);

        if (order == null) {
            throw new BadRequestException("订单信息不存在");
        }

        //判断订单支付是否超时并取消
        if (ObjectUtils.equal(order.getOrderStatus(), OrderStatusEnum.NO_PAY.getStatus())
                && order.getCreateTime().isBefore(LocalDateTime.now().minusMinutes(15))) {
            //超时未支付，取消订单
            OrderCancelDTO dto = BeanUtil.toBean(order, OrderCancelDTO.class);
            dto.setCancelReason("超时未支付自动取消");
            dto.setCurrentUserType(UserType.SYSTEM);
            ordersManagerService.cancelNoPayOrder(dto);
        }

        //封装订单信息
        OrdersDetailResDTO dto = BeanUtil.copyProperties(order, OrdersDetailResDTO.class);

        //查询订单取消信息
        OrdersCancelled ordersCancelled = ordersCancelledService.lambdaQuery()
                .eq(OrdersCancelled::getId, order.getId())
                .one();

        if (ObjectUtil.isNotEmpty(ordersCancelled)) {
            dto.setCancelTime(ordersCancelled.getCancelTime());
            dto.setCancelReason(ordersCancelled.getCancelReason());
        }

        return dto;
    }

    /**
     * 根据状态统计订单数量
     *
     * @return
     */
    @Override
    public OrdersCountResDTO countByStatus() {
//        Integer totalCount = lambdaQuery().count();
//        Integer completedCheckUpCount = lambdaQuery().eq(Orders::getOrderStatus, OrderStatusEnum.COMPLETED_CHECKUP.getStatus())
//                .count();
//        Integer waitingCheckupCount = lambdaQuery().eq(Orders::getOrderStatus, OrderStatusEnum.WAITING_CHECKUP.getStatus()).count();
//        Integer cancelledCount = lambdaQuery().eq(Orders::getOrderStatus, OrderStatusEnum.CANCELLED.getStatus())
//                .count();
//        Integer closedCount = lambdaQuery().eq(Orders::getOrderStatus, OrderStatusEnum.CLOSED.getStatus())
//                .count();
//        Integer noPayCount = lambdaQuery().eq(Orders::getOrderStatus, OrderStatusEnum.NO_PAY.getStatus()).count();
//        return new OrdersCountResDTO(noPayCount, waitingCheckupCount, completedCheckUpCount, closedCount, cancelledCount, totalCount);
        List<OrderCountDTO> orderCountList = baseMapper.countByStatus();
        OrdersCountResDTO dto = new OrdersCountResDTO();
        for (OrderCountDTO orderCountDTO : orderCountList) {
            if (ObjectUtils.equal(orderCountDTO.getOrderStatus(), OrderStatusEnum.WAITING_CHECKUP.getStatus())) {
                dto.setWaitingCheckupCount(orderCountDTO.getCount());
            }
            if (ObjectUtils.equal(orderCountDTO.getOrderStatus(), OrderStatusEnum.COMPLETED_CHECKUP.getStatus())) {
                dto.setCompletedCheckupCount(orderCountDTO.getCount());
            }
            if (ObjectUtils.equal(orderCountDTO.getOrderStatus(), OrderStatusEnum.CANCELLED.getStatus())) {
                dto.setCancelledCount(orderCountDTO.getCount());
            }
            if (ObjectUtils.equal(orderCountDTO.getOrderStatus(), OrderStatusEnum.CLOSED.getStatus())) {
                dto.setClosedCount(orderCountDTO.getCount());
            }
            if (ObjectUtils.equal(orderCountDTO.getOrderStatus(), OrderStatusEnum.NO_PAY.getStatus())) {
                dto.setNoPayCount(orderCountDTO.getCount());
            }
            dto.setTotalCount(dto.getCancelledCount() + dto.getClosedCount() + dto.getCompletedCheckupCount() + dto.getWaitingCheckupCount() + dto.getNoPayCount());
        }
        return dto;
    }
}
